package com.lxm.common.tools;

import cn.hutool.core.util.StrUtil;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * MinIO工具类
 * 提供对MinIO存储桶和文件的操作方法，如创建存储桶、上传文件、获取文件信息等。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtils {

    private final MinioClient minioClient;

    /******************************  Operate Bucket Start  ******************************/

    /**
     * 将Base64编码的字符串转换为输入流。
     *
     * @param base64 Base64编码的字符串
     * @return 转换后的输入流
     */
    public static InputStream base64ToInputStream(String base64) {
        try {
            // 将Base64编码的字符串转换为字节数组
            byte[] bytes = Base64.getDecoder().decode(base64.trim());
            return new ByteArrayInputStream(bytes);
        } catch (Exception e) {
            log.error("[Minio工具类]>>>> Base64转换为输入流失败，异常：", e);
            return null; // 如果转换失败，返回null
        }
    }

    /**
     * 初始化存储桶。若存储桶不存在则创建新桶。
     *
     * @param bucketName 存储桶名称
     */

    private void createBucket(String bucketName) throws ServerException, InsufficientDataException,
            ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidResponseException, XmlParserException, InternalException {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 检查存储桶是否存在。
     *
     * @param bucketName 存储桶名称
     * @return 存储桶存在返回true，不存在返回false
     */

    public boolean bucketExists(String bucketName) throws ServerException, InsufficientDataException,
            ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidResponseException, XmlParserException, InternalException {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 获取存储桶的策略。
     *
     * @param bucketName 存储桶名称
     * @return 存储桶策略的JSON字符串
     */

    public String getBucketPolicy(String bucketName) throws ServerException,
            InsufficientDataException, ErrorResponseException,
            BucketPolicyTooLargeException, IOException,
            NoSuchAlgorithmException, InvalidKeyException,
            InvalidResponseException, XmlParserException, InternalException {
        return minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
    }

    /**
     * 列出所有存储桶。
     *
     * @return 存储桶列表
     */

    public List<Bucket> getAllBuckets() throws ServerException, InsufficientDataException,
            ErrorResponseException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return minioClient.listBuckets();
    }

    /**
     * 根据存储桶名称获取存储桶信息。
     *
     * @param bucketName 存储桶名称
     * @return 存储桶信息的Optional对象
     */

    public Optional<Bucket> getBucket(String bucketName) throws ServerException,
            InsufficientDataException, ErrorResponseException,
            IOException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidResponseException, XmlParserException, InternalException {
        return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    /******************************  Operate Bucket End  ******************************/


    /******************************  Operate Files Start  ******************************/

    /**
     * 删除存储桶。如果存储桶包含文件则会删除失败。
     *
     * @param bucketName 存储桶名称
     */

    public void removeBucket(String bucketName) throws ServerException, InsufficientDataException,
            ErrorResponseException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 检查文件是否存在。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @return 文件存在返回true，不存在返回false
     */
    public boolean isObjectExist(String bucketName, String objectName) {
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return true;
        } catch (Exception e) {
            log.error("[Minio工具类]>>>> 判断文件是否存在, 异常：", e);
            return false; // 文件不存在或其他异常
        }
    }

    /**
     * 检查文件夹是否存在。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件夹名称
     * @return 文件夹存在返回true，不存在返回false
     */
    public boolean isFolderExist(String bucketName, String objectName) {
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && objectName.equals(item.objectName())) {
                    return true; // 文件夹存在
                }
            }
        } catch (Exception e) {
            log.error("[Minio工具类]>>>> 判断文件夹是否存在，异常：", e);
        }
        return false; // 文件夹不存在
    }

    /**
     * 根据文件前缀查询文件列表。
     *
     * @param bucketName 存储桶名称
     * @param prefix     文件前缀
     * @param recursive  是否递归查询
     * @return 匹配的文件列表
     */

    public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive)
            throws ServerException, InsufficientDataException,
            ErrorResponseException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        for (Result<Item> o : objectsIterator) {
            list.add(o.get());
        }
        return list;
    }

    /**
     * 获取指定文件的输入流。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @return 文件的输入流
     */
    public InputStream getObject(String bucketName, String objectName) throws ServerException,
            InsufficientDataException, ErrorResponseException,
            IOException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidResponseException, XmlParserException, InternalException {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 断点下载文件，指定偏移量和读取长度。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度
     * @return 指定位置的文件输入流
     */

    public InputStream getObject(String bucketName, String objectName, long offset, long length) throws ServerException,
            InsufficientDataException,
            ErrorResponseException,
            IOException,
            NoSuchAlgorithmException,
            InvalidKeyException,
            InvalidResponseException,
            XmlParserException,
            InternalException {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());
    }

    /**
     * 列出指定路径下的文件列表。
     *
     * @param bucketName 存储桶名称
     * @param prefix     文件名称前缀
     * @param recursive  是否递归查找，false时模拟文件夹结构
     * @return 文件列表
     */
    public Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive) {
        return minioClient.listObjects(ListObjectsArgs
                .builder()
                .bucket(bucketName)
                .prefix(prefix)
                .recursive(recursive).build());
    }

    /**
     * 使用MultipartFile进行文件上传，并返回文件的外链URL。
     *
     * @param bucketName  存储桶名称
     * @param file        文件对象
     * @param objectName  存储对象名称
     * @param contentType 文件类型
     * @return 上传结果对象及文件的外链URL
     */

    public Map<String, Object> uploadFileWithUrl(String bucketName, MultipartFile file,
                                                 String objectName, String contentType) throws IOException,
            ServerException, InsufficientDataException, ErrorResponseException,
            NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
            XmlParserException, InternalException {
        InputStream inputStream = file.getInputStream();
        ObjectWriteResponse response = minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .contentType(contentType)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
        String url = getPresignedObjectUrl(bucketName, objectName);
        if (!StringUtils.isEmpty(url)) url = url.split("\\?")[0];
        Map<String, Object> result = new HashMap<>();
        result.put("response", response);
        result.put("url", url);
        return result;
    }

    /**
     * 上传Base64编码的图片，并返回文件的外链URL。
     *
     * @param bucketName  存储桶名称
     * @param imageBase64 图片的Base64编码字符串
     * @param imageName   图片文件名
     * @return 上传结果对象及文件的外链URL
     */
    public Map<String, Object> uploadImageWithUrl(String bucketName, String imageBase64, String imageName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        if (StrUtil.isBlank(imageBase64)) return null;
        InputStream in = base64ToInputStream(imageBase64);
        String newName = System.currentTimeMillis() + "_" + imageName + ".jpg";
        String year = String.valueOf(Calendar.getInstance().get(Calendar.YEAR));
        String month = String.valueOf(Calendar.getInstance().get(Calendar.MONTH) + 1);

        // 直接调用处理InputStream的uploadFileWithUrl方法
        Map<String, Object> result = uploadFileWithUrl(bucketName, year + "/" + month + "/" + newName, in);
        return result;
    }


    /**
     * 上传本地文件到指定存储桶，并返回文件的外链URL。
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储对象名称
     * @param fileName   本地文件路径
     * @return 上传结果对象及文件的外链URL
     */

    public Map<String, Object> uploadFileWithUrl(String bucketName, String objectName,
                                                 String fileName) throws IOException,
            ServerException, InsufficientDataException, ErrorResponseException,
            NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
            XmlParserException, InternalException {
        ObjectWriteResponse response = minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .filename(fileName)
                        .build());
        String url = getPresignedObjectUrl(bucketName, objectName);

        Map<String, Object> result = new HashMap<>();
        result.put("response", response);
        result.put("url", url);
        return result;
    }

    /**
     * 通过输入流上传文件，并返回文件的外链URL。
     *
     * @param bucketName  存储桶名称
     * @param objectName  存储对象名称
     * @param inputStream 文件输入流
     * @return 上传结果对象及文件的外链URL
     */

    public Map<String, Object> uploadFileWithUrl(String bucketName, String objectName, InputStream inputStream) throws IOException,
            ServerException, InsufficientDataException, ErrorResponseException,
            NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
            XmlParserException, InternalException {
        ObjectWriteResponse response = minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
        String url = getPresignedObjectUrl(bucketName, objectName);

        Map<String, Object> result = new HashMap<>();
        result.put("response", response);
        result.put("url", url);
        return result;
    }


    /**
     * 创建一个空目录。
     *
     * @param bucketName 存储桶名称
     * @param objectName 目录路径
     * @return 上传结果对象
     */

    public ObjectWriteResponse createDir(String bucketName, String objectName) throws ServerException,
            InsufficientDataException, ErrorResponseException, IOException,
            NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
            XmlParserException, InternalException {
        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(new ByteArrayInputStream(new byte[]{}), 0, -1) // 创建空对象表示目录
                        .build());
    }

    /**
     * 获取指定文件的信息。如果文件不存在则抛出异常。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @return 文件状态信息的字符串
     */

    public String getFileStatusInfo(String bucketName, String objectName) throws ServerException,
            InsufficientDataException, ErrorResponseException, IOException,
            NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
            XmlParserException, InternalException {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()).toString();
    }

    /**
     * 拷贝文件到另一个存储桶。
     *
     * @param bucketName    目标存储桶
     * @param objectName    目标文件名
     * @param srcBucketName 源存储桶
     * @param srcObjectName 源文件名
     * @return 拷贝结果对象
     */

    public ObjectWriteResponse copyFile(String bucketName, String objectName, String srcBucketName, String srcObjectName)
            throws ServerException, InsufficientDataException, ErrorResponseException,
            IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
            XmlParserException, InternalException {
        return minioClient.copyObject(
                CopyObjectArgs.builder()
                        .source(CopySource.builder().bucket(srcBucketName).object(srcObjectName).build())
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
    }

    /**
     * 删除指定文件。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     */

    public boolean removeFile(String bucketName, String objectName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            return false;
        }

        return true;
    }

    /**
     * 批量删除文件。
     *
     * @param bucketName 存储桶名称
     * @param keys       需要删除的文件列表
     */
    public void removeFiles(String bucketName, List<String> keys) {
        List<DeleteObject> objects = new LinkedList<>();
        keys.forEach(s -> {
            objects.add(new DeleteObject(s));
            try {
                removeFile(bucketName, s); // 删除每个文件
            } catch (Exception e) {
                log.error("[Minio工具类]>>>> 批量删除文件，异常：", e);
            }
        });
    }

    /**
     * 获取文件的外链URL，设置有效期。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @param expires    URL过期时间（单位：秒，最大为7秒）
     * @return 生成的外链URL
     */

    public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires)
            throws ServerException, InsufficientDataException, ErrorResponseException,
            IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException,
            XmlParserException, InternalException {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .expiry(expires)
                .bucket(bucketName)
                .object(objectName)
                .build();
        return minioClient.getPresignedObjectUrl(args);
    }

    /**
     * 获取文件的外链URL，默认有效期为默认设置。
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @return 生成的外链URL
     */

    public String getPresignedObjectUrl(String bucketName, String objectName)
            throws ServerException, InsufficientDataException,
            ErrorResponseException, IOException, NoSuchAlgorithmException,
            InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .method(Method.GET) // 设置HTTP方法为GET
                .build();
        return minioClient.getPresignedObjectUrl(args);
    }

    /**
     * 将URL编码转为UTF-8格式。
     *
     * @param str 需要解码的字符串
     * @return 解码后的字符串
     * @throws UnsupportedEncodingException 如果不支持指定编码
     */
    public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
        String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25"); // 处理错误的URL编码
        return URLDecoder.decode(url, "UTF-8");
    }
}